{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit ApplicationManager;

interface

uses Classes, Graphics, ThemeManager, UIManager, ActionManager, Preferences,
     MetaData, MediaManager, ExpressionManager, Version, Animation,
     FunctionDescriptor, MetaDescriptor, Settings, MediaFiles, PluginManager,
     Timing, Persistence, Thumbnails, AHMTypes;

type
  TAHMMessageEvent = procedure(Msg: String; Source: TPersistent) of Object;

  TAHMApplicationState = (asInvalid, asInitialising, asInitialised, asRunning,
                          asRendering, asPaused, asConfiguring, asClosing,
                          asRestarting, asHalting);

  TAHMApplicationManager = class
  private
    FRemoteMessageTimestamp: TAHMTimestamp;
    FVersionManager: TAHMVersionManager;
    FThemeManager: TAHMThemeManager;
    FActionManager: TAHMActionManager;
    FExpressionManager: TAHMExpressionManager;
    FUIManager: TAHMUIManager;
    FMetaDataManager: TAHMMetaDataManager;
    FPrefsManager: TAHMPrefsManager;
    FStateManager: TAHMPersistenceManager;
    FSettingsManager: TAHMSettingsManager;
    FMediaManager: TAHMMediaManager;
    FAnimationEngine: TAHMAnimationEngine;
    FTimerManager: TAHMTimerManager;
    FMediaFilesManager: TAHMMediaFilesManager;
    FPluginManager: TAHMPluginManager;
    FMetaDescriptor: TAHMMetaDescriptor;
    FFunctionDescriptor: TAHMFunctionDescriptor;
    FThumbnailGenerator: TAHMThumbnailGenerator;
    FConfigPath: String;
    FPluginPath: String;
    FBinaryPath: String;
    FThemePath: String;
    FThemeName: String;
    FThumbPath: String;
    FReadConfiguration: Boolean;
    FFocussed: Boolean;
    FChangingState: Boolean;
    FRestarting: Boolean;
    FState: TAHMApplicationState;
    FNewState: TAHMApplicationState;
    FShutdownAction: TAHMWindowsShutdown;
    FDesignerMode: Boolean;
    FOnRestart: TNotifyEvent;
    FOnShutdown: TNotifyEvent;
    FOnModified: TNotifyEvent;
    FOnParseError: TAHMMessageEvent;
  protected
    function VersionChecked: Boolean;
    procedure AllocateResources;
    procedure AllocateConfigurationResources;
    procedure ReleaseResources;
  public
    constructor Create;
    destructor Destroy; override;
    property VersionManager: TAHMVersionManager read FVersionManager;
    property ThemeManager: TAHMThemeManager read FThemeManager;
    property ActionManager: TAHMActionManager read FActionManager;
    property ExpressionManager: TAHMExpressionManager read FExpressionManager;
    property UIManager: TAHMUIManager read FUIManager;
    property MetaDataManager: TAHMMetaDataManager read FMetaDataManager;
    property PreferencesManager: TAHMPrefsManager read FPrefsManager;
    property StateManager: TAHMPersistenceManager read FStateManager;
    property SettingsManager: TAHMSettingsManager read FSettingsManager;
    property MediaManager: TAHMMediaManager read FMediaManager;
    property TimerManager: TAHMTimerManager read FTimerManager;
    property AnimationEngine: TAHMAnimationEngine read FAnimationEngine;
    property MediaFilesManager: TAHMMediaFilesManager read FMediaFilesManager;
    property PluginManager: TAHMPluginManager read FPluginManager;
    property MetaDescriptor: TAHMMetaDescriptor read FMetaDescriptor;
    property FunctionDescriptor: TAHMFunctionDescriptor read FFunctionDescriptor;
    property ThumbnailGenerator: TAHMThumbnailGenerator read FThumbnailGenerator;
    property ConfigPath: String read FConfigPath;
    property PluginPath: String read FPluginPath;
    property BinaryPath: String read FBinaryPath;
    property ThemePath: String read FThemePath;
    property ThumbnailPath: String read FThumbPath;
    property State: TAHMApplicationState read FState;
    property ThemeName: String read FThemeName write FThemeName;
    property DesignerMode: Boolean read FDesignerMode write FDesignerMode;
    property OnRestart: TNotifyEvent read FOnRestart write FOnRestart;
    property OnShutdown: TNotifyEvent read FOnShutdown write FOnShutdown;
    property OnModified: TNotifyEvent read FOnModified write FOnModified;
    property OnParseError: TAHMMessageEvent read FOnParseError write FOnParseError;
    class procedure CreateInstance;
    class procedure DestroyInstance;
    function ReadConfiguration: Boolean;
    procedure ConfigureScreenPosition;
    procedure ReconfigureAdvancedSettings;
    procedure DisplayAbout;
    procedure ConfigureSettings;
    procedure Launch;
    function Initialise: Boolean;
    procedure Run;
    function RenderFrame: Boolean;
    procedure ShutDown(Force: Boolean = False);
    procedure Terminate(Action: TAHMWindowsShutdown); overload;
    procedure Terminate; overload;
    procedure Restart;
    procedure Halt;
    procedure SuspendRendering;
    procedure ResumeRendering;
    procedure AppActivated;
    procedure AppDeactivated;
    procedure AppHibernating;
    procedure AppResumed;
    procedure LogParseError(ErrorMsg: String; Source: TPersistent);
    procedure ImportTheme(Filename: String);
    function ChangeTheme(NewTheme: String): Boolean;
    procedure ProcessRemoteCommand(KeyCode: Word);
    function ProcessKeyPress(KeyCode: Word; Shift: TShiftState): Boolean;
    function ProcessMouseDown(X, Y: Integer): Boolean;
    function ProcessMouseMove(X, Y: Integer; Down: Boolean): Boolean;
    function ProcessMouseUp(X, Y: Integer): Boolean;
    function ProcessMouseWheel(Delta: Integer): Boolean;
    function ProcessMouseMenu: Boolean;
    function UpdateMousePosition(X, Y: Integer): Boolean;
  end;

var
  ApplicationManagerInstance: TAHMApplicationManager;

implementation

uses Forms, Dialogs, Controls, SysUtils, AHMConsts, SettingsForm, ErrorForm,
     BaseAbout, Screen, Playlist, Actions, InternetHelper, Logging, Utilities,
     MetaConsts;

function TerminateApplicationManager: Boolean;
begin
  // If application manager has not yet been shutdown cleanly then do so
  if Assigned(ApplicationManagerInstance) then
  begin
    ApplicationManagerInstance.Shutdown(True);
    TAHMApplicationManager.DestroyInstance;
  end;

  // Return true to allow termination
  Result := True;
end;

// TAHMApplicationManager

constructor TAHMApplicationManager.Create;
var
  AppHome: String;
begin
  inherited Create;

  // Set initial application state - not yet initialised
  FState := asInvalid;

  // Create logger, preferences, version, state & theme manager first
  LoggingInstance := TAHMLogger.Create;
  FPrefsManager := TAHMPrefsManager.Create;
  FVersionManager := TAHMVersionManager.Create;
  FStateManager := TAHMPersistenceManager.Create;
  FThemeManager := TAHMThemeManager.Create;

  // Set path for required application files
  AppHome := ParentFolder(ApplicationPath);
  FConfigPath := AppHome + CONFIG_FOLDER + PathDelim;
  FPluginPath := AppHome + PLUGIN_FOLDER + PathDelim;
  FBinaryPath := AppHome + BINARY_FOLDER + PathDelim;
  FThemePath := AppHome + THEMES_FOLDER + PathDelim;
  FThumbPath := AppHome + THUMBS_FOLDER + PathDelim;
  LoggingInstance.LogDirectory := AppHome + LOG_FOLDER + PathDelim;

  // Initialise logging output
  LoggingInstance.Initialise;
end;

destructor TAHMApplicationManager.Destroy;
begin
  FTimerManager.Free;
  FAnimationEngine.Free;
  FThemeManager.Free;
  FActionManager.Free;
  FExpressionManager.Free;
  FUIManager.Free;
  FSettingsManager.Free;
  FMetaDataManager.Free;
  FVersionManager.Free;
  FPrefsManager.Free;
  FMediaManager.Free;
  FMediaFilesManager.Free;
  FPluginManager.Free;
  FMetaDescriptor.Free;
  FFunctionDescriptor.Free;
  FThumbnailGenerator.Free;
  FStateManager.Free; // important - do this last!

  // Free logger instance
  FreeAndNil(LoggingInstance);

  inherited Destroy;
end;

class procedure TAHMApplicationManager.CreateInstance;
begin
  // Simplistic singleton pattern (not rigorously enforced)
  if ApplicationManagerInstance = nil then
  begin
    ApplicationManagerInstance := TAHMApplicationManager.Create;
    AddTerminateProc(TerminateApplicationManager);
  end;
end;

class procedure TAHMApplicationManager.DestroyInstance;
begin
  // Cleanup application manager singleton reference
  if Assigned(ApplicationManagerInstance) then
    FreeAndNil(ApplicationManagerInstance);
end;

function TAHMApplicationManager.VersionChecked: Boolean;
begin
  // Set default result - user wants to continue
  Result := True;

  // Is there a later version available?
  if FVersionManager.CheckNewerVersion then
  begin
    // Later version available - should we continue ?
    Result := FVersionManager.PerformUpgrade;
  end;
end;

procedure TAHMApplicationManager.AllocateResources;
begin
  // Create remaining manager objects to look after mediate
  FThemeManager.AllocateResources;
  if not Assigned(FActionManager) then FActionManager := TAHMActionManager.Create;
  if not Assigned(FExpressionManager) then FExpressionManager := TAHMExpressionManager.Create;
  if not Assigned(FUIManager) then FUIManager := TAHMUIManager.Create;
  if not Assigned(FMetaDataManager) then FMetaDataManager := TAHMMetaDataManager.Create;
  if not Assigned(FSettingsManager) then FSettingsManager := TAHMSettingsManager.Create;
  if not Assigned(FMediaFilesManager) then FMediaFilesManager := TAHMMediaFilesManager.Create;
  if not Assigned(FMediaManager) then FMediaManager := TAHMMediaManager.Create;
  if not Assigned(FTimerManager) then FTimerManager := TAHMTimerManager.Create;
  if not Assigned(FAnimationEngine) then FAnimationEngine := TAHMAnimationEngine.Create;
  if not Assigned(FPluginManager) then FPluginManager := TAHMPluginManager.Create;
  if not Assigned(FMetaDescriptor) then FMetaDescriptor := TAHMMetaDescriptor.Create;
  if not Assigned(FFunctionDescriptor) then FFunctionDescriptor := TAHMFunctionDescriptor.Create;
  if not Assigned(FThumbnailGenerator) then FThumbnailGenerator := TAHMThumbnailGenerator.Create;
end;

procedure TAHMApplicationManager.AllocateConfigurationResources;
begin
  // Use above method to instantiate our manager objects (we won't use them all)
  AllocateResources;

  // Read preferences if not yet done so (i.e. configured from command line)
  if not FReadConfiguration then
    FPrefsManager.LoadPreferences;

  // There are a few managers that need initialising for advanced config
  FActionManager.LoadKeyBindings;
  FMediaManager.LoadMediaLocations;
  FPluginManager.LoadPluginDescriptors;
end;

procedure TAHMApplicationManager.ReleaseResources;
begin
  // Finalise/unload any plugins we have loaded
  if Assigned(FPluginManager) then FPluginManager.UnloadPlugins;

  // Release manager objects and their associated resources
  FreeAndNil(FActionManager);
  FreeAndNil(FExpressionManager);
  FreeAndNil(FUIManager);
  FreeAndNil(FMetaDataManager);
  FreeAndNil(FSettingsManager);
  FreeAndNil(FMediaFilesManager);
  FreeAndNil(FMediaManager);
  FreeAndNil(FTimerManager);
  FreeAndNil(FAnimationEngine);
  FreeAndNil(FPluginManager);
  FreeAndNil(FMetaDescriptor);
  FreeAndNil(FFunctionDescriptor);
  FreeAndNil(FThumbnailGenerator);
  FThemeManager.ReleaseResources;
end;

function TAHMApplicationManager.ReadConfiguration: Boolean;
begin
  try
    // Load application preferences unless already loaded
    if not FReadConfiguration then FPrefsManager.LoadPreferences;

    // Apply application preferences to various manager objects
    if not FDesignerMode then FThemeName := FPrefsManager.ThemeName;
    ScreenInstance.FullScreen := FPrefsManager.FullScreen;
    ScreenInstance.DisplayAdapter := FPrefsManager.DisplayAdapter;
    ScreenInstance.TextureFormat := FPrefsManager.TextureFormat;
    ScreenInstance.SetPhysicalSize(FPrefsManager.ScreenWidth, FPrefsManager.ScreenHeight);
    FThemeManager.CacheDescriptors := FDesignerMode;
    FThemeManager.LanguageDescriptor.LanguageManager.PreferredLanguageName := FPrefsManager.LanguageName;
    TimerInstance.DateFormat := FPrefsManager.DateFormat;
    TimerInstance.TimeFormat := FPrefsManager.TimeFormat;
    TimerInstance.MaximumFPS := FPrefsManager.ScreenRefresh;
    FUIManager.DisplayDiagnostics := FPrefsManager.ShowDiagnostics;
    FUIManager.MouseCursor := FPrefsManager.MouseCursor;
    FActionManager.KeyProfileName := FPrefsManager.KeyProfileName;
    FUIManager.ScreenSaver.ScreenSaverDelay := FPrefsManager.ScreenSaverDelay;
    FVersionManager.CheckUpdates := FPrefsManager.CheckUpdates;

    // Finalise one off initialisation steps
    if not FReadConfiguration then
    begin
      FActionManager.LoadKeyBindings;
      FMediaManager.LoadMediaLocations;
      FMediaManager.Radio.LoadRadioSettings;
      FMediaManager.Equaliser.LoadEqualiserSettings;
      FSettingsManager.LoadMetaData(META_SETTINGS);
      FPluginManager.LoadPluginDescriptors;
      if FDesignerMode then FMetaDescriptor.LoadDescriptor;
      FFunctionDescriptor.LoadDescriptor;
      RegisterSupportedMediaTypes;
      ConfigureScreenPosition;
    end;

    // Okay, application initialised successfully
    FReadConfiguration := True;
    Result := True;
  except
    // Problem experienced during initialisation
    on E: Exception do
    begin
      FReadConfiguration := True;
      raise EConfigurationException.Create('Failed to read application configuration:' +
                                           sLineBreak + E.Message);
    end;
  end;
end;

procedure TAHMApplicationManager.ConfigureScreenPosition;
begin
  // Apply offset and scale preferences to screen instance
  if not FDesignerMode then
  begin
    ScreenInstance.ScreenXOffset := FPrefsManager.ScreenXOffset;
    ScreenInstance.ScreenYOffset := FPrefsManager.ScreenYOffset;
    ScreenInstance.ScreenXScale := FPrefsManager.ScreenXScale;
    ScreenInstance.ScreenYScale := FPrefsManager.ScreenYScale;
  end;
end;

procedure TAHMApplicationManager.ReconfigureAdvancedSettings;
begin
  // Apply advanced settings to appropriate manager objects here
  FUIManager.MouseCursor := FPrefsManager.MouseCursor;
  FActionManager.KeyProfileName := FPrefsManager.KeyProfileName;
  TimerInstance.MaximumFPS := FPrefsManager.ScreenRefresh;

  // Re-register our supported media types
  RegisterSupportedMediaTypes;

  // Load updated media folders
  FMediaManager.LoadMediaLocations;

  // Save our updated preference settings
  FPrefsManager.SavePreferences;
end;

procedure TAHMApplicationManager.DisplayAbout;
begin
  // Suspend rendering whilst showing about to avoid flicker
  SuspendRendering;
  try
    // Create and display about form
    TfrmBaseAbout.ShowAboutBox('media to extremes!');
  finally
    ResumeRendering;
  end;
end;

procedure TAHMApplicationManager.ConfigureSettings;
var
  OldState: TAHMApplicationState;
begin
  // Ensure application is in correct state to tweak settings
  OldState := FState;
  case FState of
    asInvalid: AllocateConfigurationResources;
    asInitialising, asInitialised, asClosing, asRestarting, asHalting, asConfiguring: Exit;
  end;

  // Change to configuring state so we prevent UI updates
  FState := asConfiguring;
  try
    // Create and display config form
    with TfrmSettings.Create(Application.MainForm) do
    try
      PopupParent := Application.MainForm;
      if ConfigureSettings then Self.ReconfigureAdvancedSettings;
    finally
      Free;
    end;
  finally
    // Release config resources allocated earlier
    if OldState = asInvalid then ReleaseResources;

    // Restore previous state
    FState := OldState;
  end;
end;

procedure TAHMApplicationManager.Launch;
begin
  // Launch mediate user interface - what we do depends on current state
  case FState of
    asInvalid:
    begin
      // not currently running, so initialise and run
      if Initialise then Run;
    end;
    asPaused, asRunning, asRendering:
    begin
      // already running, so make sure window is visible
      ScreenInstance.BringToFront;
    end;
  end;
end;

function TAHMApplicationManager.Initialise: Boolean;
begin
  // Assume initialisation will fail
  Result := False;

  // Check current state - don't initialise twice
  if FState <> asInvalid then Exit;

  FState := asInitialising;
  try
    // Before we do anything else retrieve the previous application state
    FStateManager.LoadState;

    // Allocate resources for our manager objects and initialise screen instance
    AllocateResources;
    ScreenInstance.Initialise;

    // Read application configuration settings
    if ReadConfiguration then
    begin
      // Check for new application version
      if VersionChecked then
      begin
        // Okay, all initialised and ready
        Result := True;
        FState := asInitialised;
        FShutdownAction := wsNone;
      end;
    end;
  finally
    // Ensure we remain in known state if any errors occur
    if FState = asInitialising then
    begin
      FState := asInvalid;
      FReadConfiguration := False;
    end;
  end;
end;

procedure TAHMApplicationManager.Run;
begin
  if FState = asInitialised then
  try
    // Disable system balloon tips if required
    if FPrefsManager.HideBalloonTips then DisableSystemBalloonTips;

    // Now we can load our theme
    if FThemeManager.LoadTheme(FThemeName) then
    begin
      FUIManager.InitialiseTheme;
      TimerInstance.InitialiseTimer;
      ScreenInstance.DisplayScreen;

      // Okay, mediate is now running
      FState := asRunning;
      FFocussed := True;
    end;
  except
    // If configuration read okay, but failed to start then shutdown forcefully
    on E: Exception do
      if not FDesignerMode then
      begin
        TfrmError.ShowError('mediate has failed to start - this may be due to ' +
                            'configuration settings which are unsuitable for ' +
                            'your computer. Please review the error message ' +
                            'below for more details:', E.Message);
        Application.Terminate;
      end
      else
      begin
        // Let the theme designer handle this
        raise;
      end;
  end;
end;

function TAHMApplicationManager.RenderFrame: Boolean;
begin
  // Set default result - not rendered
  Result := False;

  // We can only render when in a running or paused state
  if FState in [asRunning, asPaused] then
  begin
    // Change state so we don't attempt to render again
    FNewState := FState;
    FState := asRendering;
    try
      // Ask the UI manager to update its internal state
      TimerInstance.BeginFrameTiming;
      TimerInstance.BeginProfiling('User interface updates');
      FUIManager.UpdateUI;
      TimerInstance.EndProfiling('User interface updates');

      // Only render when we have focus and are running (not when paused)
      if FFocussed and (FNewState = asRunning) then
      begin
        // Wait until it's time for the next frame, and if we're not running
        // too slowly then ask UI manager to render itself to the screen
        if TimerInstance.WaitForNextFrame then
          FUIManager.RenderUI;
      end
      else
      begin
        // Don't render the UI as we are either paused or lost focus, so
        // instead keep the screensaver awake (so it won't appear when we
        // regain focus) and yield to other windows applications
        FUIManager.ScreenSaver.DidSomething;
        Sleep(200);
      end;

      // Return success - render complete
      TimerInstance.EndFrameTiming;
      Result := True;
    finally
      // Restore previous application state unless we were asked to restart,
      // shutdown or halt during the render phase - in which case we fulfil
      // relevant request now that rendering is completed
      FState := FNewState;
      if FState in [asClosing, asRestarting, asHalting] then
      begin
        FState := asInitialised;
        case FNewState of
          asClosing: ShutDown;
          asRestarting: Restart;
          asHalting: Halt;
        end;
      end;
    end;
  end;
end;

procedure TAHMApplicationManager.ShutDown(Force: Boolean);
begin
  // If we are still rendering, then log request to shutdown but do nothing yet
  if FState = asRendering then
    if Force then FState := asPaused
  else
  begin
    FNewState := asClosing;
    Exit;
  end;

  // Must be in appropriate state to commence shutdown
  if not (FState in [asInitialised, asRunning, asPaused]) then Exit;

  // Signal that we're closing down application
  FState := asClosing;
  try
    // Signal event notification
    if Assigned(FOnShutdown) then FOnShutdown(Self);

    // Ask rendering screen instance to shut down
    ScreenInstance.ShutDown;

    // Save updated preferences and other settings
    FPrefsManager.SavePreferences;
    FMediaManager.Radio.SaveRadioSettings;
    FMediaManager.Equaliser.SaveEqualiserSettings;

    // Release our manager objects and held resources
    ReleaseResources;

    // Last thing we should do is to save application state
    FStateManager.SaveState;

    // Restore system balloon tips original status
    if FPrefsManager.HideBalloonTips then ResetSystemBalloonTips;
  finally
    // We must be re-initialised now to continue
    FState := asInvalid;
    FReadConfiguration := False;
    FRemoteMessageTimestamp := 0;

    // Shutdown windows now if requested to earlier
    case FShutdownAction of
      wsSleep:     ExitWindows(ewSleep);
      wsReboot:    ExitWindows(ewRestart);
      wsPowerOff:  ExitWindows(ewPowerOff);
      wsHibernate: ExitWindows(ewHibernate);
    end;
  end;
end;

procedure TAHMApplicationManager.Terminate(Action: TAHMWindowsShutdown);
begin
  // Record windows shutdown action to perform after closing mediate
  FShutdownAction := Action;
  Terminate;
end;

procedure TAHMApplicationManager.Terminate;
begin
  // Shutdown gracefully then close mediate completely
  Shutdown;
  Application.Terminate;
end;

procedure TAHMApplicationManager.Restart;
begin
  // If we are still rendering, then log request to restart but do nothing yet
  if FState = asRendering then
  begin
    FNewState := asRestarting;
    Exit;
  end;

  // Must be in appropriate state to commence restart
  if not (FState in [asInitialised, asRunning, asPaused]) then Exit;

  // Shutdown then reinitialise application
  try
    FRestarting := True;
    ShutDown;
    if Initialise then Run;

    // Signal event notification
    if Assigned(FOnRestart) then FOnRestart(Self);
  finally
    FRestarting := False;
  end;
end;

procedure TAHMApplicationManager.Halt;
begin
  if FState in [asHalting] then Exit;

  // Change to halting status and abort rendering
  FState := asHalting;
  try
    ScreenInstance.Halt;
  finally
    FState := asInvalid;
  end;
end;

procedure TAHMApplicationManager.SuspendRendering;
begin
  // Change state to suspended
  if (not FChangingState) and (FState in [asRunning]) then
    FState := asPaused;
end;

procedure TAHMApplicationManager.ResumeRendering;
begin
  // Change state to running and bring mediate to front
  if (not FChangingState) and (FState in [asPaused]) then
  try
    FChangingState := True;
    FState := asRunning;
    ScreenInstance.BringToFront;
  finally
    FChangingState := False;
  end;
end;

procedure TAHMApplicationManager.AppActivated;
begin
  // We only care about focus changes when in rendering state
  if (not FChangingState) and (FState in [asPaused, asRendering, asRunning]) then
  try
    FChangingState := True;
    FFocussed := True;

    // Wake the screensaver when we regain focus
    FUIManager.ScreenSaver.DidSomething;

    if ScreenInstance.FullScreen then
    begin
      ScreenInstance.BringToFront;
      ScreenInstance.ResetDisplay;
    end;
  finally
    FChangingState := False;
  end;
end;

procedure TAHMApplicationManager.AppDeactivated;
begin
  // We only care about focus changes when in rendering state
  if (not FChangingState) and (FState in [asPaused, asRendering, asRunning]) then
  try
    FChangingState := True;

    // If we suspiciously lose focus just after starting a video then there
    // is a strong possibility that the cheesy DivX tray app has just appeared,
    // so we wrestle focus back to mediate to avoid spoiling our user experience
    if FMediaManager.HasVideoJustStarted then
      ScreenInstance.BringToFront
    else if ScreenInstance.FullScreen then
    begin
      FFocussed := False; // don't stop rendering if windowed
//      Application.Minimize; // hide cheesy icon on desktop
    end;
  finally
    FChangingState := False;
  end;
end;

procedure TAHMApplicationManager.AppHibernating;
begin
  // Windows is hibernating, we just need to inform plugins
  if FState in [asRunning, asRendering, asPaused, asConfiguring] then
    if Assigned(FPluginManager) then FPluginManager.HibernatePlugins;
end;

procedure TAHMApplicationManager.AppResumed;
begin
  // Windows has resumed from suspended state, so inform plugins
  if FState in [asRunning, asRendering, asPaused, asConfiguring] then
    if Assigned(FPluginManager) then FPluginManager.ResumePlugins;
end;

procedure TAHMApplicationManager.LogParseError(ErrorMsg: String; Source: TPersistent);
begin
  // Hand off to event if assigned, otherwise raise suitable exception
  if Assigned(FOnParseError) then
    FOnParseError(ErrorMsg, Source)
  else
    raise EParsingException.Create(ErrorMsg);
end;

procedure TAHMApplicationManager.ImportTheme(Filename: String);
const
  MSG_TITLE_IMFAIL = 'Theme Import Failed';
  MSG_HEAD_IMFAIL  = 'Failed to import theme';
  MSG_TEXT_IMPINV  = 'The specified file does not appear to be a valid mediate theme archive:';
  MSG_TITLE_IMEXST = 'Overwrite Theme?';
  MSG_HEAD_IMEXST  = 'This theme already exists';
  MSG_TEXT_IMEXST  = 'A theme named %s already exists. Would you like to overwrite ' +
                     'this theme with the contents of the theme archive:';
  MSG_TITLE_IMPORT = 'Theme Imported';
  MSG_HEAD_IMPORT  = 'Theme successfully imported';
  MSG_TEXT_IMPORT  = 'The theme %s was successfully imported.';
  MSG_TEXT_IMFAIL  = 'The theme %s could not be imported into mediate. ' +
                     sLineBreak + sLineBreak +
                     'Please check that this is a valid mediate theme archive ' +
                     'and that you have permission to write to the theme folder:';
var
  NewName, NewDesc: String;
  Status: Boolean;
begin
  // Validate archive and determine name of theme to import
  if not FThemeManager.ValidateThemeArchive(Filename, NewName, NewDesc) then
  begin
    TaskMsgDialog(MSG_TITLE_IMFAIL, MSG_HEAD_IMFAIL, MSG_TEXT_IMPINV +
                  sLineBreak + sLineBreak + Filename, mtError, [mbOk], 0, mbOk);
    Exit;
  end;

  // Concatenate name and description for user feedback messages
  if NewDesc <> '' then NewDesc := Format('"%s" (%s)', [NewName, NewDesc])
                   else NewDesc := Format('"%s"', [NewName]);

  // Now check for an existing theme with that name
  if not FThemeManager.ValidateThemeName(NewName, True) then
  begin
    if TaskMsgDialog(MSG_TITLE_IMEXST, MSG_HEAD_IMEXST, Format(MSG_TEXT_IMEXST, [NewDesc]) +
                     sLineBreak + sLineBreak + Filename, mtConfirmation,
                     [mbYes, mbNo], 0, mbYes) = mrNo then Exit;
  end;

  // Good to go, install the new theme
  Forms.Screen.Cursor := crHourglass;
  try
    Status := FThemeManager.ImportTheme(Filename, NewName);
  finally
    Forms.Screen.Cursor := crDefault;
  end;

  // Display final success/failure message
  if Status then
    TaskMsgDialog(MSG_TITLE_IMPORT, MSG_HEAD_IMPORT, Format(MSG_TEXT_IMPORT, [NewDesc]),
                  mtInformation, [mbOk], 0, mbOk)
  else
    TaskMsgDialog(MSG_TITLE_IMFAIL, MSG_HEAD_IMFAIL, Format(MSG_TEXT_IMFAIL, [NewDesc]) +
                  sLineBreak + sLineBreak + Filename, mtError, [mbOk], 0, mbOk);
end;

function TAHMApplicationManager.ChangeTheme(NewTheme: String): Boolean;
begin
//TODO ChangeTheme not implemented
  Result := False;
end;

procedure TAHMApplicationManager.ProcessRemoteCommand(KeyCode: Word);
begin
  // Prevent remote commands queuing up faster than we can handle them
  if (FRemoteMessageTimestamp > 0) then
    if not TimerInstance.HasDurationElapsed(FRemoteMessageTimestamp, 10) then Exit;
  FRemoteMessageTimestamp := TimerInstance.CurrentTimeStamp;

  // Check application state to determine how we should handle remote command
  if FState in [asInvalid] then
  begin
    // Not initialised, so attempt to launch mediate, but ignore command
    if Initialise then Run;
  end
  else if FState in [asRunning, asRendering, asPaused] then
  begin
    // Allow UI manager to handle the remote event
    FUIManager.ProcessRemoteCommand(KeyCode);
  end;
end;

function TAHMApplicationManager.ProcessKeyPress(KeyCode: Word; Shift: TShiftState): Boolean;
begin
  // Set default result - not handled
  Result := False;

  // Must be running to handle keypress events
  if FState in [asRunning, asRendering, asPaused] then
  begin
    // Allow UI manager to handle the key event
    FUIManager.ProcessKeyPress(KeyCode, Shift);
    Result := True;
  end;
end;

function TAHMApplicationManager.ProcessMouseDown(X, Y: Integer): Boolean;
begin
  // Set default result - not handled
  Result := False;

  // Must be running but not rendering to handle mouse events
  if FState in [asRunning] then
  begin
    // Allow UI manager to handle the mouse event
    Result := FUIManager.ProcessMouseDown(X, Y);
  end;
end;

function TAHMApplicationManager.ProcessMouseMove(X, Y: Integer; Down: Boolean): Boolean;
begin
  // Set default result - not handled
  Result := False;

  // Must be running but not rendering to handle mouse events
  if FState in [asRunning] then
  begin
    // Allow UI manager to handle the mouse event
    Result := FUIManager.ProcessMouseMove(X, Y, Down);
  end;
end;

function TAHMApplicationManager.ProcessMouseUp(X, Y: Integer): Boolean;
begin
  // Set default result - not handled
  Result := False;

  // Must be running but not rendering to handle mouse events
  if FState in [asRunning] then
  begin
    // Allow UI manager to handle the mouse event
    Result := FUIManager.ProcessMouseUp(X, Y);
  end;
end;

function TAHMApplicationManager.ProcessMouseWheel(Delta: Integer): Boolean;
begin
  // Set default result - not handled
  Result := False;

  // Must be running but not rendering to handle mouse events
  if FState in [asRunning] then
  begin
    // Allow UI manager to handle the mouse event
    Result := FUIManager.ProcessMouseWheel(Delta);
  end;
end;

function TAHMApplicationManager.ProcessMouseMenu: Boolean;
begin
  // Set default result - not handled
  Result := False;

  // Must be running but not rendering to handle mouse events
  if FState in [asRunning] then
  begin
    // Allow UI manager to handle the mouse event
    Result := FActionManager.ProcessAction(AC_MENU);
  end;
end;

function TAHMApplicationManager.UpdateMousePosition(X, Y: Integer): Boolean;
begin
  // Set default result - not handled
  Result := False;

  // Must be running but not rendering to handle mouse events
  if FState in [asRunning] then
  begin
    // Allow UI manager to handle the mouse event
    Result := FUIManager.UpdateMousePosition(X, Y);
  end;
end;

end.




